Security News
GitHub Removes Malicious Pull Requests Targeting Open Source Repositories
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Social Media Photo by Andrii Ganzevych on Unsplash
micro html is a ~2.5K lighterhtml subset to build declarative and reactive UI via template literals tags.
Please ask questions in the dedicated discussions repository, to help the community around this project grow ♥
import {html, foreign} from 'uhtml';
const handler = (node, name, value) => {
// P, any, {data: 123}
console.log(node, name, value);
// return null/undefined to remove it
return value.data;
};
html`<p any=${foreign(handler, {data: 123})}>foreign</p>`;
function
, enabling a world of µhtml extending possibilities, including intents, hence aligning the behavior with both lighterhtml and hyperHTML. That is: <el>${callback}</el>
! The callback
will be invoked with the comment pin/placeholder as unique argument, where its parentNode
would be the element containing such comment, if needed, and its returned value will be passed along the same mechanism that resolves already all other cases.?attribute=${value}
prefix, with a question mark, has landed, after this long debate, and based to the fact µhtml never wants to be ambiguous. However, the mighty lit-html put an end to the debate, disambiguating through a ?
question mark prefix, which explicits developers intents, and works well across browsers. So that's it: whenever you expect an attribute to be in, or out, use ?name=${value}
and win the day 🥳new.js
file has been renamed as es.js
to align with other modules of mine that follow the same pattern.uhtml/async
, in order to automatically resolve asynchronous values passed along the template. Please note this means that exported render
, html
, and svg
tags, are all asynchronous, hence these all return a promise.async.js
file is now published too, compatible with ES2015+ browsers (no async
/ await
used)Example
import {render, html, svg} from 'uhtml/async';
render(document.body, html`a${Promise.resolve('b')}c`);
Install the module via npm i uhtml
and consume it as such:
import {render, html, svg} from 'uhtml';
// const {render, html, svg} = require('uhtml');
render(document.body, html`<h1>Hello 👋 µhtml</h1>`);
Alternatively you can use a CDN such as unpkg, as shown in this demo.
<script src="https://unpkg.com/uhtml">/* global uhtml */</script>
<!-- or -->
<script type="module">
import {render, html, svg} from 'https://unpkg.com/uhtml?module';
</script>
Most information about µhtml are written in the documentation file, but following you can read most essential details.
The module exports the following functionalities:
render(where, what)
function to populate the where
DOM node with what
content, which can be a DOM node, or the returning value of html
and svg
tags. The render
function returns the where
DOM node itself.html
template literal tag, to produce any sort of HTML content.svg
template literal tag, to produce any sort of SVG content.html
and svg
implements a .for(reference[, id])
template tag function for keyed weak relationships within the node. Please don't overuse this feature, as 90% of the time is not necessary, and it could make the rendering slower than it should. Also, consider the ref
attribute, in case a reference to the current node is needed at any time.html
and svg
implements a .node
template tag function for one-off HTML or SVG creation. Please don't use html.node
one off nodes within render(...)
calls, as this utility exists to help creating fragments or nodes that should be manually added to the DOM, and not through render
calls.Any element can have one or more attribute, either interpolated or not.
render(document.body, html`
<div id="main"
class=${`content ${extra}`}
data-fancy=${fancy}>
<p contenteditable=${editable}
@click=${listener}
onclick=${listener}
class="${['container', 'user'].join(' ')}">
Hello ${user.name}, feel free to edit this content.
</p>
</div>
`);
These are the rules to follow for attributes:
name=${value}
is OK, and so is name="${value}"
or even name='${value}'
style="top:${x};left${y}"
as the parser will simply break with the error bad template. Use template literals within interpolations, if you want to obtain exact same result: style=${`top:${x};left${y}`}
null
or undefined
, the attribute will be removed. If the value is something else, it will be set as is as value. If the attribute was previously removed, the same attribute will be placed back again. If the value is the same as it was before, nothing happenson
or @
, as example, onclick=${...}
or @click=${...}
, it will be set as listener. If the listener changes, the previous one will be automatically removed. If the listener is an Array
like [listener, {once:true}]
, the second entry of the array would be used as listener's options..
dot, as in .setter=${value}
, the value will be passed directly to the element per each update. If such value is a known setter, either native elements or defined via Custom Elements, the setter will be invoked per each update, even if the value is the same?
question mark, as in ?hidden=${value}
, the value will be toggled, accordingly with its truthy, or falsy, value.ref
, as in ref=${object}
, the object.current
property will be assigned to the node, once this is rendered, and per each update. If a callback is passed instead, the callback will receive the node right away, same way React ref does. Please note that conditional renders will not cleanup the reference, if this is not assigned to the new node.aria
, as in aria=${object}
, aria attributes are applied to the node, including the role
one..dataset
, as in .dataset=${object}
, the node.dataset
gets populated with all values.Following an example of both aria
and data
cases:
// the aria special case
html`<div aria=${{labelledBy: 'id', role: 'button'}} />`;
//=> <div aria-labelledby="id" role="button"></div>
// the data special case
html`<div .dataset=${{key: 'value', otherKey: 'otherValue'}} />`;
//=> <div data-key="value" data-other-key="otherValue"></div>
It is possible to place interpolations within any kind of node, and together with text or other nodes too.
render(document.body, html`
<table>
${lines.map((text, i) => html`
<tr><td>Row ${i} with text: ${text}</td></tr>
`)}
</table>
`);
There are only few exceptional nodes that do not allow sparse content within themselves:
<plaintext>${content}</plaintext>
, deprecated, yet it cannot contain comments<script>${content}</script>
, it can contain comments, but only whole text can be replaced<style>${content}</style>
, it cannot contain comments<textarea>${content}</textarea>
, same as above<title>${content}</title>
, same as above<xmp>${content}</xmp>
, same as aboveFollowing an example on how to populate these nodes (wrong + right way):
// DON'T DO THIS
render(document.body, html`
<style>
body { font-size: ${fontSize}; }
</style>
<textarea>
Write here ${user.name}
</textarea>
`);
// DO THIS INSTEAD
render(document.body, html`
<style>
${`
body { font-size: ${fontSize}; }
`}
</style>
<textarea>
${`
Write here ${user.name}
`}
</textarea>
`);
Beside nodes where the content will be inevitably just text, like it is for style
or textarea
, as example, every other interpolation can contain primitives, as strings, numbers, or even booleans, or the returned value of html
or svg
, plus regular DOM nodes.
The only special case are Array of either primitives, or returned values from html
or svg
.
render(document.body, html`
<ul>
<li>This is ${'primitive'}</li>
<li>This is joined as primitives: ${[1, 2, 3]}</li>
${lines.map((text, i) => html`
<li>Row ${i} with content: ${text}</li>
`)}
</ul>
`);
The second what
argument of the render(where, what)
signature can be either a function, which returning value will be used to populate the content, the result of html
or svg
tags, or a DOM node, so that it is possible to render within a render.
const Button = selector => {
const button = document.querySelector(selector);
return count => render(button, html`Clicks: ${count}`);
};
const Clicker = selector => {
const button = Button(selector);
return function update(count) {
return render(document.body, html`
<div onclick=${() => update(++count)}>
Click again:
${button(count)}
</div>
`);
};
}
const clicker = Clicker('#btn-clicker');
clicker(0);
µhtml html
and svg
tags implement exact same API offered by lighterhtml.
This means that both html.for(reference[, id])
and svg.for(reference[, id])
will weakly relate the node with the reference, and an optional unique id, instead of using its internal auto-referenced algorithm.
render(document.body, html`
<ul>
${items.map(item => html.for(item)`
<li>Keyed row with content: ${item.text}</li>
`)}
</ul>
`);
In more than one occasion developers got bitten by the fact µhtml can produce some cryptic error when null
, or empty content, is provided as interpolation/hole.
The problem is pretty simple:
A basic example would be the following:
const dirtyData = [
{name: 'first'},
null,
{name: 'second'}
];
render(document.body, html`
<ul>
${dirtyData.map(item => {
// ⚠ this should not happen in the first place!
if (!item)
return null;
return html`<li>${item.name}</li>`;
})}
</ul>
`);
There are at least two workarounds to consider:
data.filter(item => validate(item)).map(...)
if (!item)
return html`<!--ignore-->`;
Between these two workarounds, I believe the cleanest one is the former: sanitize data upfront!
The benefits are:
Custom Elements are either brought you, in a simplified manner, via µce (micro custom elements), which is a 3K library based on µhtml, or via vanilla JS, as demoed in WebComponents.dev.
This module works in IE11, Edge, and every other Desktop to Mobile browser, including KaiOS.
You could read an exhaustive summary of features differences, but the first thing to keep in mind, is that lighterhtml is at pair with uhtml features, but not vice-versa, meaning if you need anything more, you can always switch to lighterhtml later on, and without changing a single line of code.
Following a list of other points to consider when choosing µhtml instead of lighterhtml (or vice-versa).
attribute=${value}
is OK, attribute="${a}${b}"
is not, and attribute="some ${'partial'}"
is not allowed neither.style
attribute is not special at all: if you want to pass objects there, please transform these as you prefer.template
argument is not normalized. If you target browsers with issue with such argument, please be sure you transpile your code with latest Babel before shipping to productionhtml
and svg
are allowed like in lighterhtml. The version 0
of this library didn't allow that, hence it was more "surprise prone". uhtml in that sense is more like a drop-in replacement for lighterhtml, and vice-versahtmlfor(...)
or svg.for(...)
, as well as one-off node creation, via html.node
or svg.node
are the same found in lighterhtmlaria=${object}
, and ref=${...}
special attributes work same as lighterhtml.dataset=${object}
helper work the same as lighterhtml.property=${...}
direct setter is still available<custom-elements />
or even <span />
@ungap/create-content
, needed only for IE11, but removable via @ungap/degap, same way I've done it here, or babel-plugin-remove-ungap. The compressed final size difference is just around ~0.2K though.The recently introduced data
helper could conflict with some node such as <object>
, hence it has been replaced by the .dataset
utility. Since element.dataset = object
is an invalid operation, the sugar to simplify data-
attributes is now never ambiguous and future-proof: <element .dataset=${...} />
it is.
FAQs
A micro HTML/SVG render
The npm package uhtml receives a total of 1,099 weekly downloads. As such, uhtml popularity was classified as popular.
We found that uhtml demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.